#!/usr/bin/env python3

import sys
import os
import subprocess
from pathlib import Path

# Use the run.py library from ../cqlpy:
sys.path.insert(1, sys.path[0] + '/../cqlpy')
import run

print('Scylla is: ' + run.find_scylla() + '.')

# Gdb will only work if the executable was built with debug symbols
# (e.g., Scylla's release or debug build modes mode, but not dev mode).
# Check this quickly up-front, instead of waiting for gdb to fail in a
# mysterious way when it can't look up types.
if not '.debug_info' in subprocess.run(['objdump', '-h', run.scylla],
        capture_output=True, text=True).stdout:
    print(f'Scylla executable was compiled without debugging information (-g)')
    print(f'so cannot be used to test gdb. Please set SCYLLA environment variable.')
    exit(1)

# Run Scylla, waiting until it can respond to CQL
pid = run.run_with_temporary_dir(run.run_scylla_cmd)
ip = run.pid_to_ip(pid)
run.wait_for_services(pid, [lambda: run.check_cql(ip)])

# We do something strange here: We start pytest *inside* gdb's Python
# interpreter. This will allow us to test various gdb commands added
# by scylla-gdb.py inside gdb using the pytest framework.
# TODO: consider a more straightforward implementation, where we don't
# run pytest inside gdb - and instead run gdb as a separate process and
# pytest just sends commands to it.
# TODO: think if we can avoid code duplication with run.run_ptest().
def run_pytest_in_gdb(pytest_dir, executable, additional_parameters):
    sys.stdout.flush()
    sys.stderr.flush()
    pid = os.fork()
    if pid == 0:
        # child:
        run.run_with_temporary_dir_pids = set() # no children to clean up on child
        run.run_pytest_pids = set()
        os.chdir(pytest_dir)
        pytest_args = ['-o', 'junit_family=xunit2'] + additional_parameters
        pytest_cmd = f'print("Starting pytest {" ".join(pytest_args)}"); import pytest; sys.argv[0]="pytest"; sys.exit(pytest.main({str(pytest_args)}))'
        print(f'Starting gdb {executable}')
        sys.stdout.flush()
        args = ['gdb',
                '-batch', '-n',
                '-ex', 'set python print-stack full',
                '-ex', 'python ' + pytest_cmd,
                ]
        if executable:
            args += ['-se', executable]
        os.execvp('gdb', args)
        exit(1)
    # parent:
    run.run_pytest_pids.add(pid)
    if os.waitpid(pid, 0)[1]:
        return False
    else:
        return True


def modify_junit_xml_filename(args, filename_modifier):
    """
    Modify the filename part of --junit-xml parameter while preserving the path.

    Args:
        args: List of command line arguments
        filename_modifier: suffix to append to the filename part of --junit-xml

    Returns:
        Modified list of arguments
    """
    if filename_modifier is None:
        return args

    modified_args = []
    for arg in args:
        if arg.startswith('--junit-xml='):
            junit_xml = Path(arg.split('=', 1)[1])
            new_junit_xml = junit_xml.with_suffix(f'.{filename_modifier}{junit_xml.suffix}')
            modified_args.append(f'--junit-xml={new_junit_xml}')
        else:
            modified_args.append(arg)

    return modified_args

# Interesting note: We must use "--scylla-tmp-dir=DIR" here instead of
# "--scylla-tmp-dir DIR": While the latter does work, pytest has a bug that
# its command-line parser finds the given directory name in the original
# command line, saves it as "initialpaths", and uses it to print what it
# thinks are nice (but are really incorrect) relative paths for "nodes" (test
# source files).
success = True
for with_scylla in [True, False]:
    modified_argv = None
    if with_scylla:
        args = ['--scylla-pid='+str(pid),
                '--scylla-tmp-dir='+run.pid_to_dir(pid),
                '-m', 'not without_scylla']
        executable = run.scylla
        modified_argv = modify_junit_xml_filename(sys.argv[1:], 'with_scylla')
    else:
        args = ['-m', 'without_scylla']
        executable = ''
        modified_argv = modify_junit_xml_filename(sys.argv[1:], 'without_scylla')
    if not run_pytest_in_gdb(sys.path[0], executable, args + modified_argv):
        success = False

run.summary = 'Scylla GDB tests pass' if success else 'Scylla GDB tests failure'

exit(0 if success else 1)

# Note that the run.cleanup_all() function runs now, just like on any exit
# for any reason in this script. It will delete the temporary files and
# announce the failure or success of the test (printing run.summary).
